﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DotNetOpenAuth.OpenId.Provider;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OpenId.RelyingParty;
using System.Web.Security;
using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
using BattleThis.Core;
using System.Net.Mail;

namespace BattleThis.Controllers
{
	public class AccountController :Controller
	{
		#region Variablen
		public const string LogOnAction = "LogOn";
        public const string LogOutAction = "LogOut";
		public const string RecognizeAction = "Recognize";
		private const string UserData = "UserData";
		public static readonly ReflectedControllerDescriptor Info = new ReflectedControllerDescriptor( typeof( AccountController ) );
		#endregion

		#region Properties
		#endregion

		#region Methoden

		/// <summary>
		/// Logs the on the user or shows the log on view.
		/// </summary>
		/// <returns>
		/// This action is called twice the second is a async call from openId, actionfilter will
		/// not be executed the OnActionExecuted so we make a Redirect to the <see cref="RecognizeUser"/>.
		/// </returns>
		[HttpGet, ActionName( LogOnAction )]
		public ActionResult LogOn()
		{
			var openid = new OpenIdRelyingParty();
			IAuthenticationResponse response = openid.GetResponse();

			if( response != null )
			{
				switch( response.Status )
				{
					case AuthenticationStatus.Authenticated:
						this.TempData.Clear();
						this.TempData.Add( UserData, response.GetExtension<FetchResponse>() );
						return	RedirectToAction( RecognizeAction );
					case AuthenticationStatus.Canceled:
						ModelState.AddModelError( "loginIdentifier", "Login was cancelled at the provider" );
						break;
					case AuthenticationStatus.Failed:
						ModelState.AddModelError( "loginIdentifier", "Login failed using the provided OpenID identifier" );
						break;
				}
			}

			return View();
		}

		/// <summary>
		/// Gets the Post-Data an send them to the openid Provider.
		/// </summary>
		/// <param name="openid_identifier">The openid_identifier.</param>
		/// <returns></returns>
		[System.Web.Mvc.AcceptVerbs( HttpVerbs.Post ), ActionName( LogOnAction ), Persistence]
		public ActionResult LogOn( string openid_identifier )
		{
			if( !Identifier.IsValid( openid_identifier ) )
			{
				ModelState.AddModelError( "loginIdentifier", "The specified login identifier is invalid" );
				return View();
			}
			else
			{
				var openid = new OpenIdRelyingParty();
				var request = openid.CreateRequest( Identifier.Parse( openid_identifier ) );

				// Require some additional data
				request.AddExtension( new ClaimsRequest
				{
					BirthDate = DemandLevel.NoRequest,
					Email = DemandLevel.Require,
					FullName = DemandLevel.Require
				} );

				return request.RedirectingResponse.AsActionResult();
			}
		}

        /// <summary>
        /// Logs the current user out.
        /// </summary>
        /// <returns></returns>
        [HttpGet, ActionName(LogOutAction)]
        public RedirectToRouteResult LogOut()
        {
            Session.Clear();
            FormsAuthentication.SignOut();

            return this.RedirectToAction(LogOnAction);
        }

        /// <summary>
        /// Recognizes the user.
        /// </summary>
        /// <returns></returns>
		[HttpGet, ActionName( RecognizeAction ), Persistence]
		public ActionResult RecognizeUser()
		{
			if( !this.TempData.ContainsKey( UserData ) )
			{ 
				return new HttpStatusCodeResult(404);
			}

			var fetchResponse = (FetchResponse) this.TempData[UserData];
			var result = this.LogOnBattleThis( fetchResponse );
			FormsAuthentication.SetAuthCookie( result.User.Name, false );

			if( result.IsNewUser )
			{
				return RedirectToAction( UserController.EditAction, UserController.Info.ControllerName );
			}
			else
			{
				return RedirectToAction( LobbyController.IndexAction, LobbyController.Info.ControllerName );
			}

		}

		/// <summary>
		/// Log on to the BattleThis application.
		/// </summary>
		/// <param name="fetchrespone">OpenId fetch response.</param>
		private LogOnResult LogOnBattleThis( FetchResponse fetchrespone )
		{
			var userdata = GetUserDataFromOpenId( fetchrespone );
			var repository = this.HttpContext.GetRepository<BattleThis.Core.User>();
			var result = new LogOnResult( userdata, repository );

			if( result.IsNewUser )
			{
				repository.Add( result.User );
			}

			return result;

		}

		/// <summary>
		/// Gets the user data from open id login.
		/// </summary>
		/// <param name="fetchresponse">The fetchrespone.</param>
		/// <returns></returns>
		private Dictionary<string, string> GetUserDataFromOpenId( FetchResponse fetchresponse )
		{
			if( fetchresponse == null )throw new ArgumentNullException( "fetchrespone" );

			var userdata = new Dictionary<string, string>();
			foreach( var attribs in fetchresponse.Attributes )
			{
				var key = attribs.TypeUri.Split( '/' ).ToList().Last();
				if( !userdata.ContainsKey( key ) )
				{
					var result =  attribs.Values[ 0 ].ToString();
					userdata.Add( key, result );
				}
			}
			return userdata;
		}

		#endregion

		#region NestedClass

		/// <summary>
		/// Result of logon to battlethis.
		/// </summary>
		[System.Diagnostics.DebuggerDisplay( "User {User} IsNewUser {IsNewUser}" )]
		public class LogOnResult
		{

			#region Variablen

			public bool IsNewUser { get; set; }
			public User User { get; set; }

			#endregion

			#region Properties

			#endregion

			#region Methoden

			/// <summary>
			/// Initializes a new instance of the <see cref="LogOnResult"/> class.
			/// </summary>
			/// <param name="userDataMap">The user data map.</param>
			/// <param name="repository">The repository.</param>
			public LogOnResult( Dictionary<string, string> userDataMap, IRepository<User> repository )
			{

				if( userDataMap == null ) throw new ArgumentNullException( "userDataMap" );
				if( repository == null ) throw new ArgumentNullException( "repository" );

				var mail = userDataMap[ "email" ];
				BattleThis.Core.User user = repository.Find( x => x.MailAdress.Equals( mail ) ).SingleOrDefault();

				this.IsNewUser = ( user == null );

				if( this.IsNewUser )
				{
					this.User = new BattleThis.Core.User();
					this.User.Id = Guid.NewGuid().ToString();
					this.User.MailAdress = mail;
					this.User.Name = new MailAddress( mail ).User;
				}
				else
				{
					this.User = user;
				}
			}

			#endregion

		}

		#endregion

	}
}
